// Copyright 2013 Ronald Schlenker, www.technewlogic.de.
// 
// This file is part of Byond.
// 
// Byond is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 2 of the License, or
// (at your option) any later version.
// 
// Byond is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with Byond.  If not, see <http://www.gnu.org/licenses/>.

using System;
using Byond.AudioMidi;
using Byond.Standalone.Messages;
using PortAudioSharp;
using Utils.Messaging;

namespace Byond.Standalone.PortAudioStuff
{
	internal sealed class PortAudioEngine : AudioEngine, IAudio, IDisposable
	{
		private PortAudioWrapper _portAudioWrapper;

		public PortAudioEngine()
		{
			_sampleRate = 44100;
			BufferSize = 512;
		}

		public PortAudio.PaDeviceInfo InputDevice { get; private set; }
		public PortAudio.PaDeviceInfo OutputDevice { get; private set; }

		private double[][] _inBuffers;
		private double[][] _outBuffers;

		public new event MessageEventHandler SendMessage;

		public void HandleMessage(TransportPlayRequestMessage message)
		{
			_isSongPlaying = true;
			SendMessage(new TransportPlayingMessage());
		}

		public void HandleMessage(TransportPauseRequestMessage message)
		{
			_isSongPlaying = false;
			SendMessage(new TransportPausingMessage());
		}

		public void HandleMessage(SongTempoChangeRequestMessage message)
		{
			_songTempo = message.Value;
		}

		public uint BufferSize { get; private set; }

		private double _songTempo = 120d;
		public override double SongTempo { get { return _songTempo; } }

		private double _sampleRate;
		public override double SampleRate { get { return _sampleRate; } }

		private long _samplePosition;
		public override long SamplePosition { get { return _samplePosition; } }

		private bool _isSongPlaying;
		public override bool IsSongPlaying { get { return _isSongPlaying; } }

		private int _sampleCountInCycle;
		protected override int SampleCountInCycle { get { return _sampleCountInCycle; } }

		//protected override void OnVoicesProcessing()
		//{
		//}

		protected override void OnVoicesProcessed()
		{
			if (_isSongPlaying)
				_samplePosition++;
		}

		protected override double[] GetOutputChannelSamples(int channel)
		{
			return _outBuffers[channel];
		}

		private PortAudio.PaStreamCallbackResult AudioCallback(
			IntPtr audioInputBuffer,
			IntPtr audioOutputBuffer,
			uint blockSize,
			ref PortAudio.PaStreamCallbackTimeInfo timeInfo,
			PortAudio.PaStreamCallbackFlags statusFlags,
			IntPtr userData)
		{
			var inputChannelCount = 0;
			_inBuffers = new double[0][];
			var outputChannelCount = 0;
			_outBuffers = new double[0][];

			lock (this)
			{
				// initialize input buffer
				inputChannelCount = InputDevice.maxInputChannels;
				_inBuffers = new double[inputChannelCount][];
				for (var n = 0; n < inputChannelCount; n++)
					_inBuffers[n] = new double[blockSize];

				// initialize output buffer
				outputChannelCount = OutputDevice.maxOutputChannels;
				_outBuffers = new double[outputChannelCount][];
				for (var n = 0; n < outputChannelCount; n++)
					_outBuffers[n] = new double[blockSize];
			}

			_sampleCountInCycle = (int)blockSize;
			ProcessAudio();

			unsafe
			{
				// Fill audio buffer for all channels, multiply samples by volume range [0, 1]
				var audioBuffer = (float*)audioOutputBuffer.ToPointer();

				var i = 0;
				var j = 0;
				while (i < blockSize * outputChannelCount)
				{
					for (var k = 0; k < outputChannelCount; k++)
					{
						audioBuffer[i] = (float)(_outBuffers[k][j]);
						i++;
					}

					j++;
				}
			}

			return PortAudio.PaStreamCallbackResult.paContinue;
		}

		public void ReStart(
			int sampleRate,
			uint bufferSize,
			PortAudio.PaDeviceInfo inputDevice,
			PortAudio.PaDeviceInfo outputDevice)
		{
			DisposePortAudio();

			var updateValues = new Action(() =>
				{
					_sampleRate = sampleRate;
					BufferSize = bufferSize;
					InputDevice = inputDevice;
					OutputDevice = outputDevice;
				});

			if (inputDevice.Equals(PortAudioExtensions.NoDevice)
				&& outputDevice.Equals(PortAudioExtensions.NoDevice))
			{
				updateValues();
				return;
			}

			lock (this)
			{
				var portAudioWrapper = new PortAudioWrapper(
					sampleRate, bufferSize, inputDevice, outputDevice, AudioCallback);
				portAudioWrapper.Start();

				updateValues();
				_portAudioWrapper = portAudioWrapper;
			}
		}

		public double GetChannelValue(int channel)
		{
			return _inBuffers.Length > channel ? _inBuffers[channel][CurrentCycle] : 0;
		}

		public void SetChannelValue(int channel, double value)
		{
			if (_outBuffers.Length > channel)
				_outBuffers[channel][CurrentCycle] = value;
		}

		public void Dispose()
		{
			DisposePortAudio();
		}

		private void DisposePortAudio()
		{
			if (_portAudioWrapper == null)
				return;

			_portAudioWrapper.Stop();
			_portAudioWrapper.Dispose();
			_portAudioWrapper = null;
		}
	}
}